// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2016, Google, Inc. All rights reserved
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#include <ctype.h>
#include <debug.h>
#include <err.h>
#include <inttypes.h>
#include <lib/console.h>
#include <string.h>

#include <dev/pcie_bridge.h>
#include <dev/pcie_bus_driver.h>
#include <dev/pcie_device.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>

class PcieDebugConsole {
 public:
  static int CmdLsPci(int argc, const cmd_args* argv, uint32_t flags);
  static int CmdPciUnplug(int argc, const cmd_args* argv, uint32_t flags);
  static int CmdPciReset(int argc, const cmd_args* argv, uint32_t flags);
  static int CmdPciRescan(int argc, const cmd_args* argv, uint32_t flags);
  static int CmdPciRegionDump(int argc, const cmd_args* argv, uint32_t flags);
};

/* Class code/Subclass code definitions taken from
 * http://wiki.osdev.org/Pci#Class_Codes */
typedef struct {
  uint8_t class_code;
  uint8_t subclass;
  uint8_t prof_if_start;
  uint8_t prof_if_end;
  const char* desc;
} pci_dev_type_lut_entry_t;

typedef struct lspci_params {
  bool verbose;
  uint base_level;
  uint indent_level;
  uint bus_id;
  uint dev_id;
  uint func_id;
  uint cfg_dump_amt;
  bool force_dump_cfg;
  uint found;
} lspci_params_t;

#define WILDCARD_ID (0xFFFFFFFF)

#define LUT_ENTRY(_class, _subclass, _pif_start, _pif_end, _desc)             \
  {                                                                           \
    .class_code = _class, .subclass = _subclass, .prof_if_start = _pif_start, \
    .prof_if_end = _pif_end, .desc = _desc,                                   \
  }

#define LUT_ENTRY_ONE_PIF(_class, _subclass, _pif, _desc) \
  LUT_ENTRY(_class, _subclass, _pif, _pif, _desc)

#define LUT_ENTRY_ALL_PIF(_class, _subclass, _desc) LUT_ENTRY(_class, _subclass, 0x00, 0xFF, _desc)

static const pci_dev_type_lut_entry_t PCI_DEV_TYPE_LUT[] = {
    LUT_ENTRY_ONE_PIF(0x00, 0x00, 0x00, "Any device except for VGA-Compatible devices"),
    LUT_ENTRY_ONE_PIF(0x00, 0x01, 0x00, "VGA-Compatible Device"),
    LUT_ENTRY_ONE_PIF(0x01, 0x00, 0x00, "SCSI Bus Controller"),
    LUT_ENTRY_ALL_PIF(0x01, 0x01, "IDE Controller"),
    LUT_ENTRY_ONE_PIF(0x01, 0x02, 0x00, "Floppy Disk Controller"),
    LUT_ENTRY_ONE_PIF(0x01, 0x03, 0x00, "IPI Bus Controller"),
    LUT_ENTRY_ONE_PIF(0x01, 0x04, 0x00, "RAID Controller"),
    LUT_ENTRY_ONE_PIF(0x01, 0x05, 0x20, "ATA Controller (Single DMA)"),
    LUT_ENTRY_ONE_PIF(0x01, 0x05, 0x30, "ATA Controller (Chained DMA)"),
    LUT_ENTRY_ONE_PIF(0x01, 0x06, 0x00, "Serial ATA (Vendor Specific Interface)"),
    LUT_ENTRY_ONE_PIF(0x01, 0x06, 0x01, "Serial ATA (AHCI 1.0)"),
    LUT_ENTRY_ONE_PIF(0x01, 0x07, 0x00, "Serial Attached SCSI (SAS)"),
    LUT_ENTRY_ONE_PIF(0x01, 0x08, 0x01, "Non-Volatile Memory Controller (NVMHCI)"),
    LUT_ENTRY_ONE_PIF(0x01, 0x08, 0x02, "Non-Volatile Memory Controller (NVM Express)"),
    LUT_ENTRY_ONE_PIF(0x01, 0x80, 0x00, "Other Mass Storage Controller"),
    LUT_ENTRY_ONE_PIF(0x02, 0x00, 0x00, "Ethernet Controller"),
    LUT_ENTRY_ONE_PIF(0x02, 0x01, 0x00, "Token Ring Controller"),
    LUT_ENTRY_ONE_PIF(0x02, 0x02, 0x00, "FDDI Controller"),
    LUT_ENTRY_ONE_PIF(0x02, 0x03, 0x00, "ATM Controller"),
    LUT_ENTRY_ONE_PIF(0x02, 0x04, 0x00, "ISDN Controller"),
    LUT_ENTRY_ONE_PIF(0x02, 0x05, 0x00, "WorldFip Controller"),
    LUT_ENTRY_ALL_PIF(0x02, 0x06, "PICMG 2.14 Multi Computing"),
    LUT_ENTRY_ONE_PIF(0x02, 0x80, 0x00, "Other Network Controller"),
    LUT_ENTRY_ONE_PIF(0x03, 0x00, 0x00, "VGA-Compatible Controller"),
    LUT_ENTRY_ONE_PIF(0x03, 0x00, 0x01, "8512-Compatible Controller"),
    LUT_ENTRY_ONE_PIF(0x03, 0x01, 0x00, "XGA Controller"),
    LUT_ENTRY_ONE_PIF(0x03, 0x02, 0x00, "3D Controller (Not VGA-Compatible)"),
    LUT_ENTRY_ONE_PIF(0x03, 0x80, 0x00, "Other Display Controller"),
    LUT_ENTRY_ONE_PIF(0x04, 0x00, 0x00, "Video Device"),
    LUT_ENTRY_ONE_PIF(0x04, 0x01, 0x00, "Audio Device"),
    LUT_ENTRY_ONE_PIF(0x04, 0x02, 0x00, "Computer Telephony Device"),
    LUT_ENTRY_ONE_PIF(0x04, 0x80, 0x00, "Other Multimedia Device"),
    LUT_ENTRY_ONE_PIF(0x05, 0x00, 0x00, "RAM Controller"),
    LUT_ENTRY_ONE_PIF(0x05, 0x01, 0x00, "Flash Controller"),
    LUT_ENTRY_ONE_PIF(0x05, 0x80, 0x00, "Other Memory Controller"),
    LUT_ENTRY_ONE_PIF(0x06, 0x00, 0x00, "Host Bridge"),
    LUT_ENTRY_ONE_PIF(0x06, 0x01, 0x00, "ISA Bridge"),
    LUT_ENTRY_ONE_PIF(0x06, 0x02, 0x00, "EISA Bridge"),
    LUT_ENTRY_ONE_PIF(0x06, 0x03, 0x00, "MCA Bridge"),
    LUT_ENTRY_ONE_PIF(0x06, 0x04, 0x00, "PCI-to-PCI Bridge"),
    LUT_ENTRY_ONE_PIF(0x06, 0x04, 0x01, "PCI-to-PCI Bridge (Subtractive Decode)"),
    LUT_ENTRY_ONE_PIF(0x06, 0x05, 0x00, "PCMCIA Bridge"),
    LUT_ENTRY_ONE_PIF(0x06, 0x06, 0x00, "NuBus Bridge"),
    LUT_ENTRY_ONE_PIF(0x06, 0x07, 0x00, "CardBus Bridge"),
    LUT_ENTRY_ALL_PIF(0x06, 0x08, "RACEway Bridge"),
    LUT_ENTRY_ONE_PIF(0x06, 0x09, 0x40, "PCI-to-PCI Bridge (Semi-Transparent, Primary)"),
    LUT_ENTRY_ONE_PIF(0x06, 0x09, 0x80, "PCI-to-PCI Bridge (Semi-Transparent, Secondary)"),
    LUT_ENTRY_ONE_PIF(0x06, 0x0A, 0x00, "InfiniBrand-to-PCI Host Bridge"),
    LUT_ENTRY_ONE_PIF(0x06, 0x80, 0x00, "Other Bridge Device"),
    LUT_ENTRY_ONE_PIF(0x07, 0x00, 0x00, "Generic XT-Compatible Serial Controller"),
    LUT_ENTRY_ONE_PIF(0x07, 0x00, 0x01, "16450-Compatible Serial Controller"),
    LUT_ENTRY_ONE_PIF(0x07, 0x00, 0x02, "16550-Compatible Serial Controller"),
    LUT_ENTRY_ONE_PIF(0x07, 0x00, 0x03, "16650-Compatible Serial Controller"),
    LUT_ENTRY_ONE_PIF(0x07, 0x00, 0x04, "16750-Compatible Serial Controller"),
    LUT_ENTRY_ONE_PIF(0x07, 0x00, 0x05, "16850-Compatible Serial Controller"),
    LUT_ENTRY_ONE_PIF(0x07, 0x00, 0x06, "16950-Compatible Serial Controller"),
    LUT_ENTRY_ONE_PIF(0x07, 0x01, 0x00, "Parallel Port"),
    LUT_ENTRY_ONE_PIF(0x07, 0x01, 0x01, "Bi-Directional Parallel Port"),
    LUT_ENTRY_ONE_PIF(0x07, 0x01, 0x02, "ECP 1.X Compliant Parallel Port"),
    LUT_ENTRY_ONE_PIF(0x07, 0x01, 0x03, "IEEE 1284 Controller"),
    LUT_ENTRY_ONE_PIF(0x07, 0x01, 0xFE, "IEEE 1284 Target Device"),
    LUT_ENTRY_ONE_PIF(0x07, 0x02, 0x00, "Multiport Serial Controller"),
    LUT_ENTRY_ONE_PIF(0x07, 0x03, 0x00, "Generic Modem"),
    LUT_ENTRY_ONE_PIF(0x07, 0x03, 0x01, "Hayes Compatible Modem (16450-Compatible Interface)"),
    LUT_ENTRY_ONE_PIF(0x07, 0x03, 0x02, "Hayes Compatible Modem (16550-Compatible Interface)"),
    LUT_ENTRY_ONE_PIF(0x07, 0x03, 0x03, "Hayes Compatible Modem (16650-Compatible Interface)"),
    LUT_ENTRY_ONE_PIF(0x07, 0x03, 0x04, "Hayes Compatible Modem (16750-Compatible Interface)"),
    LUT_ENTRY_ONE_PIF(0x07, 0x04, 0x00, "IEEE 488.1/2 (GPIB) Controller"),
    LUT_ENTRY_ONE_PIF(0x07, 0x05, 0x00, "Smart Card"),
    LUT_ENTRY_ONE_PIF(0x07, 0x80, 0x00, "Other Communications Device"),
    LUT_ENTRY_ONE_PIF(0x08, 0x00, 0x00, "Generic 8259 PIC"),
    LUT_ENTRY_ONE_PIF(0x08, 0x00, 0x01, "ISA PIC"),
    LUT_ENTRY_ONE_PIF(0x08, 0x00, 0x02, "EISA PIC"),
    LUT_ENTRY_ONE_PIF(0x08, 0x00, 0x10, "I/O APIC Interrupt Controller"),
    LUT_ENTRY_ONE_PIF(0x08, 0x00, 0x20, "I/O(x) APIC Interrupt Controller"),
    LUT_ENTRY_ONE_PIF(0x08, 0x01, 0x00, "Generic 8237 DMA Controller"),
    LUT_ENTRY_ONE_PIF(0x08, 0x01, 0x01, "ISA DMA Controller"),
    LUT_ENTRY_ONE_PIF(0x08, 0x01, 0x02, "EISA DMA Controller"),
    LUT_ENTRY_ONE_PIF(0x08, 0x02, 0x00, "Generic 8254 System Timer"),
    LUT_ENTRY_ONE_PIF(0x08, 0x02, 0x01, "ISA System Timer"),
    LUT_ENTRY_ONE_PIF(0x08, 0x02, 0x02, "EISA System Timer"),
    LUT_ENTRY_ONE_PIF(0x08, 0x03, 0x00, "Generic RTC Controller"),
    LUT_ENTRY_ONE_PIF(0x08, 0x03, 0x01, "ISA RTC Controller"),
    LUT_ENTRY_ONE_PIF(0x08, 0x04, 0x00, "Generic PCI Hot-Plug Controller"),
    LUT_ENTRY_ONE_PIF(0x08, 0x80, 0x00, "Other System Peripheral"),
    LUT_ENTRY_ONE_PIF(0x09, 0x00, 0x00, "Keyboard Controller"),
    LUT_ENTRY_ONE_PIF(0x09, 0x01, 0x00, "Digitizer"),
    LUT_ENTRY_ONE_PIF(0x09, 0x02, 0x00, "Mouse Controller"),
    LUT_ENTRY_ONE_PIF(0x09, 0x03, 0x00, "Scanner Controller"),
    LUT_ENTRY_ONE_PIF(0x09, 0x04, 0x00, "Gameport Controller (Generic)"),
    LUT_ENTRY_ONE_PIF(0x09, 0x04, 0x10, "Gameport Contrlller (Legacy)"),
    LUT_ENTRY_ONE_PIF(0x09, 0x80, 0x00, "Other Input Controller"),
    LUT_ENTRY_ONE_PIF(0x0a, 0x00, 0x00, "Generic Docking Station"),
    LUT_ENTRY_ONE_PIF(0x0a, 0x80, 0x00, "Other Docking Station"),
    LUT_ENTRY_ONE_PIF(0x0b, 0x00, 0x00, "386 Processor"),
    LUT_ENTRY_ONE_PIF(0x0b, 0x01, 0x00, "486 Processor"),
    LUT_ENTRY_ONE_PIF(0x0b, 0x02, 0x00, "Pentium Processor"),
    LUT_ENTRY_ONE_PIF(0x0b, 0x10, 0x00, "Alpha Processor"),
    LUT_ENTRY_ONE_PIF(0x0b, 0x20, 0x00, "PowerPC Processor"),
    LUT_ENTRY_ONE_PIF(0x0b, 0x30, 0x00, "MIPS Processor"),
    LUT_ENTRY_ONE_PIF(0x0b, 0x40, 0x00, "Co-Processor"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x00, 0x00, "IEEE 1394 Controller (FireWire)"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x00, 0x10, "IEEE 1394 Controller (1394 OpenHCI Spec)"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x01, 0x00, "ACCESS.bus"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x02, 0x00, "SSA"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x03, 0x00, "USB (Universal Host Controller Spec)"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x03, 0x10, "USB (Open Host Controller Spec)"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x03, 0x20, "USB2 Host Controller (Intel EHCI)"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x03, 0x30, "USB3 XHCI Controller"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x03, 0x80, "Unspecified USB Controller"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x03, 0xFE, "USB (Not Host Controller)"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x04, 0x00, "Fibre Channel"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x05, 0x00, "SMBus"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x06, 0x00, "InfiniBand"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x07, 0x00, "IPMI SMIC Interface"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x07, 0x01, "IPMI Kybd Controller Style Interface"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x07, 0x02, "IPMI Block Transfer Interface"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x08, 0x00, "SERCOS Interface Standard (IEC 61491)"),
    LUT_ENTRY_ONE_PIF(0x0c, 0x09, 0x00, "CANbus"),
    LUT_ENTRY_ONE_PIF(0x0d, 0x00, 0x00, "iRDA Compatible Controller"),
    LUT_ENTRY_ONE_PIF(0x0d, 0x01, 0x00, "Consumer IR Controller"),
    LUT_ENTRY_ONE_PIF(0x0d, 0x10, 0x00, "RF Controller"),
    LUT_ENTRY_ONE_PIF(0x0d, 0x11, 0x00, "Bluetooth Controller"),
    LUT_ENTRY_ONE_PIF(0x0d, 0x12, 0x00, "Broadband Controller"),
    LUT_ENTRY_ONE_PIF(0x0d, 0x20, 0x00, "Ethernet Controller (802.11a)"),
    LUT_ENTRY_ONE_PIF(0x0d, 0x21, 0x00, "Ethernet Controller (802.11b)"),
    LUT_ENTRY_ONE_PIF(0x0d, 0x80, 0x00, "Other Wireless Controller"),
    LUT_ENTRY(0x0e, 0x00, 0x01, 0xFF, "I20 Architecture"),
    LUT_ENTRY_ONE_PIF(0x0e, 0x00, 0x00, "Message FIFO"),
    LUT_ENTRY_ONE_PIF(0x0f, 0x01, 0x00, "TV Controller"),
    LUT_ENTRY_ONE_PIF(0x0f, 0x02, 0x00, "Audio Controller"),
    LUT_ENTRY_ONE_PIF(0x0f, 0x03, 0x00, "Voice Controller"),
    LUT_ENTRY_ONE_PIF(0x0f, 0x04, 0x00, "Data Controller"),
    LUT_ENTRY_ONE_PIF(0x10, 0x00, 0x00, "Network and Computing Encrpytion/Decryption"),
    LUT_ENTRY_ONE_PIF(0x10, 0x10, 0x00, "Entertainment Encryption/Decryption"),
    LUT_ENTRY_ONE_PIF(0x10, 0x80, 0x00, "Other Encryption/Decryption"),
    LUT_ENTRY_ONE_PIF(0x11, 0x00, 0x00, "DPIO Modules"),
    LUT_ENTRY_ONE_PIF(0x11, 0x01, 0x00, "Performance Counters"),
    LUT_ENTRY_ONE_PIF(0x11, 0x10, 0x00, "Communications Syncrhonization"),
    LUT_ENTRY_ONE_PIF(0x11, 0x20, 0x00, "Management Card"),
    LUT_ENTRY_ONE_PIF(0x11, 0x80, 0x00, "Other Data Acquisition/Signal Processing Controller"),
};

#undef LUT_ENTRY
#undef LUT_ENTRY_ONE_PIF
#undef LUT_ENTRY_ALL_PIF

static const char* pci_class_code_to_string(uint8_t class_code) {
  switch (class_code) {
    case 0x00:
      return "Pre-Class Code Device";
    case 0x01:
      return "Mass Storage Controller";
    case 0x02:
      return "Network Controller";
    case 0x03:
      return "Display Controller";
    case 0x04:
      return "Multimedia Controller";
    case 0x05:
      return "Memory Controller";
    case 0x06:
      return "Bridge Device";
    case 0x07:
      return "Simple Communication Controller";
    case 0x08:
      return "Base System Peripheral";
    case 0x09:
      return "Input Device";
    case 0x0A:
      return "Docking Station";
    case 0x0B:
      return "Processor";
    case 0x0C:
      return "Serial Bus Controller";
    case 0x0D:
      return "Wireless Controller";
    case 0x0E:
      return "Intelligent I/O Controller";
    case 0x0F:
      return "Satellite Communication Controller";
    case 0x10:
      return "Encryption/Decryption Controller";
    case 0x11:
      return "Data Acquisition or Signal Processing Controller";
    case 0xFF:
      return "Vendor";
    default:
      return "<Unknown>";
  }
}

static const char* pci_device_type(const PcieDevice& dev) {
  // TODO(johngro): It might be a good idea, some day, to make this something
  // better than an O(n) search.

  // If this is a PCIe style bridge with a specific device type spelled out in
  // its PCI Express Capabilities structure, use that to provide the type
  // string.
  switch (dev.pcie_device_type()) {
    case PCIE_DEVTYPE_RC_ROOT_PORT:
      return "PCIe Root Port";
    case PCIE_DEVTYPE_SWITCH_UPSTREAM_PORT:
      return "PCIe Upstream Switch Port";
    case PCIE_DEVTYPE_SWITCH_DOWNSTREAM_PORT:
      return "PCIe Downstream Switch Port";
    case PCIE_DEVTYPE_PCIE_TO_PCI_BRIDGE:
      return "PCIe-to-PCI Bridge";
    case PCIE_DEVTYPE_PCI_TO_PCIE_BRIDGE:
      return "PCI-to-PCIe Bridge";
    default:
      break;
  }

  for (size_t i = 0; i < fbl::count_of(PCI_DEV_TYPE_LUT); ++i) {
    const pci_dev_type_lut_entry_t* entry = PCI_DEV_TYPE_LUT + i;

    if ((dev.class_id() == entry->class_code) && (dev.subclass() == entry->subclass) &&
        (dev.prog_if() >= entry->prof_if_start) && (dev.prog_if() <= entry->prof_if_end))
      return entry->desc;
  }

  return pci_class_code_to_string(dev.class_id());
}

static void do_lspci_indent(uint level) {
  while (level--)
    printf("  ");
}
#define LSPCI_PRINTF(_fmt, ...)            \
  do {                                     \
    do_lspci_indent(params->indent_level); \
    printf(_fmt, ##__VA_ARGS__);           \
  } while (0)

static void dump_pcie_hdr(const PcieDevice& dev, lspci_params_t* params) {
  DEBUG_ASSERT(params);
  LSPCI_PRINTF("[%02x:%02x.%01x] - VID 0x%04x DID 0x%04x :: %s", dev.bus_id(), dev.dev_id(),
               dev.func_id(), dev.vendor_id(), dev.device_id(), pci_device_type(dev));

  if (dev.disabled())
    printf(" [DISABLED]");

  printf("\n");
}

static void dump_pcie_bars(const PcieDevice& dev, lspci_params_t* params) {
  auto cfg = dev.config();

  DEBUG_ASSERT(dev.bar_count() <= PCIE_MAX_BAR_REGS);
  for (uint i = 0; i < dev.bar_count(); ++i) {
    LSPCI_PRINTF("Base Addr[%u]      : 0x%08x", i, cfg->Read(PciConfig::kBAR(i)));

    const pcie_bar_info_t* info = dev.GetBarInfo(i);
    if (info == nullptr) {
      printf("\n");
      continue;
    }

    printf(" :: paddr %#" PRIx64 " size %#" PRIx64 "%s%s %s%s\n", info->bus_addr, info->size,
           info->is_prefetchable ? " prefetchable" : "",
           info->is_mmio ? (info->is_64bit ? " 64-bit" : " 32-bit") : "",
           info->is_mmio ? "MMIO" : "PIO", info->allocation == nullptr ? "" : " (allocated)");
  }
}

static void dump_pcie_common(const PcieDevice& dev, lspci_params_t* params) {
  auto cfg = dev.config();
  uint8_t base_class = cfg->Read(PciConfig::kBaseClass);

  LSPCI_PRINTF("Command           : 0x%04x\n", cfg->Read(PciConfig::kCommand));
  LSPCI_PRINTF("Status            : 0x%04x\n", cfg->Read(PciConfig::kStatus));
  LSPCI_PRINTF("Rev ID            : 0x%02x\n", cfg->Read(PciConfig::kRevisionId));
  LSPCI_PRINTF("Prog Iface        : 0x%02x\n", cfg->Read(PciConfig::kProgramInterface));
  LSPCI_PRINTF("Sub Class         : 0x%02x\n", cfg->Read(PciConfig::kSubClass));
  LSPCI_PRINTF("Base Class        : 0x%02x %s\n", base_class, pci_class_code_to_string(base_class));
  LSPCI_PRINTF("Cache Line Sz     : 0x%02x\n", cfg->Read(PciConfig::kCacheLineSize));
  LSPCI_PRINTF("Latency Timer     : 0x%02x\n", cfg->Read(PciConfig::kLatencyTimer));
  LSPCI_PRINTF("Header Type       : 0x%02x\n", cfg->Read(PciConfig::kHeaderType));
  LSPCI_PRINTF("BIST              : 0x%02x\n", cfg->Read(PciConfig::kBist));
}

static void dump_pcie_standard(const PcieDevice& dev, lspci_params_t* params) {
  auto cfg = dev.config();
  LSPCI_PRINTF("Cardbus CIS       : 0x%08x\n", cfg->Read(PciConfig::kCardbusCisPtr));
  LSPCI_PRINTF("Subsystem VID     : 0x%04x\n", cfg->Read(PciConfig::kSubsystemVendorId));
  LSPCI_PRINTF("Subsystem ID      : 0x%04x\n", cfg->Read(PciConfig::kSubsystemId));
  LSPCI_PRINTF("Exp ROM addr      : 0x%08x\n", cfg->Read(PciConfig::kExpansionRomAddress));
  LSPCI_PRINTF("Cap Ptr           : 0x%02x\n", cfg->Read(PciConfig::kCapabilitiesPtr));
  LSPCI_PRINTF("IRQ line          : 0x%02x\n", cfg->Read(PciConfig::kInterruptLine));
  LSPCI_PRINTF("IRQ pin           : 0x%02x\n", cfg->Read(PciConfig::kInterruptPin));
  LSPCI_PRINTF("Min Grant         : 0x%02x\n", cfg->Read(PciConfig::kMinGrant));
  LSPCI_PRINTF("Max Latency       : 0x%02x\n", cfg->Read(PciConfig::kMaxLatency));
}

static void dump_pcie_bridge(const PcieBridge& bridge, lspci_params_t* params) {
  auto cfg = bridge.config();

  LSPCI_PRINTF("P. Bus ID         : 0x%02x\n", cfg->Read(PciConfig::kPrimaryBusId));
  LSPCI_PRINTF("S. Bus Range      : [0x%02x, 0x%02x]\n", cfg->Read(PciConfig::kSecondaryBusId),
               cfg->Read(PciConfig::kSubordinateBusId));
  LSPCI_PRINTF("S. Latency Timer  : 0x%02x\n", cfg->Read(PciConfig::kSecondaryLatencyTimer));
  LSPCI_PRINTF("IO Base           : 0x%02x\n", cfg->Read(PciConfig::kIoBase));
  LSPCI_PRINTF("IO Base Upper     : 0x%04x\n", cfg->Read(PciConfig::kIoBaseUpper));
  LSPCI_PRINTF("IO Limit          : 0x%02x\n", cfg->Read(PciConfig::kIoLimit));
  LSPCI_PRINTF("IO Limit Upper    : 0x%04x", cfg->Read(PciConfig::kIoLimitUpper));
  if (bridge.io_base() < bridge.io_limit()) {
    printf(" :: [0x%08x, 0x%08x]\n", bridge.io_base(), bridge.io_limit());
  } else {
    printf("\n");
  }
  LSPCI_PRINTF("Secondary Status  : 0x%04x\n", cfg->Read(PciConfig::kSecondaryStatus));
  LSPCI_PRINTF("Memory Limit      : 0x%04x\n", cfg->Read(PciConfig::kMemoryLimit));
  LSPCI_PRINTF("Memory Base       : 0x%04x", cfg->Read(PciConfig::kMemoryBase));
  if (bridge.mem_base() < bridge.mem_limit()) {
    printf(" :: [0x%08x, 0x%08x]\n", bridge.mem_base(), bridge.mem_limit());
  } else {
    printf("\n");
  }
  LSPCI_PRINTF("PFMem Base        : 0x%04x\n", cfg->Read(PciConfig::kPrefetchableMemoryBase));
  LSPCI_PRINTF("PFMem Base Upper  : 0x%08x\n", cfg->Read(PciConfig::kPrefetchableMemoryBaseUpper));
  LSPCI_PRINTF("PFMem Limit       : 0x%04x\n", cfg->Read(PciConfig::kPrefetchableMemoryLimit));
  LSPCI_PRINTF("PFMem Limit Upper : 0x%08x", cfg->Read(PciConfig::kPrefetchableMemoryLimitUpper));
  if (bridge.pf_mem_base() < bridge.pf_mem_limit()) {
    printf(" :: [0x%016" PRIx64 ", 0x%016" PRIx64 "]\n", bridge.pf_mem_base(),
           bridge.pf_mem_limit());
  } else {
    printf("\n");
  }

  LSPCI_PRINTF("Capabilities Ptr  : 0x%02x\n", cfg->Read(PciConfig::kCapabilitiesPtr));
  LSPCI_PRINTF("Exp ROM Address   : 0x%08x\n", cfg->Read(PciConfig::kExpansionRomAddress));
  LSPCI_PRINTF("Interrupt Line    : 0x%02x\n", cfg->Read(PciConfig::kInterruptLine));
  LSPCI_PRINTF("Interrupt Pin     : 0x%02x\n", cfg->Read(PciConfig::kInterruptPin));
  LSPCI_PRINTF("Bridge Control    : 0x%04x\n", cfg->Read(PciConfig::kBridgeControl));
}

static void dump_pcie_raw_config(uint amt, const PciConfig* cfg) {
  DEBUG_ASSERT(amt == PCIE_BASE_CONFIG_SIZE || amt == PCIE_EXTENDED_CONFIG_SIZE);
  cfg->DumpConfig(static_cast<uint16_t>(amt & 0xFFFF));
}

#define CAP_TBL_ENTRY(s) (s, #s)
static struct _cap_tbl {
  uint8_t id;
  const char* label;
} cap_tbl[] = {
    {PCIE_CAP_ID_PCI_PWR_MGMT, "PCI_PWR_MGMT"},
    {PCIE_CAP_ID_AGP, "AGP"},
    {PCIE_CAP_ID_VPD, "VPD"},
    {PCIE_CAP_ID_MSI, "MSI"},
    {PCIE_CAP_ID_PCIX, "PCIX"},
    {PCIE_CAP_ID_HYPERTRANSPORT, "HYPERTRANSPORT"},
    {PCIE_CAP_ID_VENDOR, "VENDOR"},
    {PCIE_CAP_ID_DEBUG_PORT, "DEBUG_PORT"},
    {PCIE_CAP_ID_COMPACTPCI_CRC, "COMPACTPCI_CRC"},
    {PCIE_CAP_ID_PCI_HOTPLUG, "PCI_HOTPLUG"},
    {PCIE_CAP_ID_PCI_BRIDGE_SUBSYSTEM_VID, "PCI_BRIDGE_SUBSYSTEM_VID"},
    {PCIE_CAP_ID_AGP_8X, "AGP_8X"},
    {PCIE_CAP_ID_SECURE_DEVICE, "SECURE_DEVICE"},
    {PCIE_CAP_ID_PCI_EXPRESS, "PCI_EXPRESS"},
    {PCIE_CAP_ID_MSIX, "MSIX"},
    {PCIE_CAP_ID_SATA_DATA_NDX_CFG, "SATA_DATA_NDX_CFG"},
    {PCIE_CAP_ID_ADVANCED_FEATURES, "ADVANCED_FEATURES"},
    {PCIE_CAP_ID_ENHANCED_ALLOCATION, "ENHANCED_ALLOCATION"},
};
#undef CAP_TABLE_ENTRY

static inline const char* get_cap_str(uint8_t id) {
  for (const auto& cur : cap_tbl) {
    if (cur.id == id) {
      return cur.label;
    }
  }

  return "<Unknown>";
}

static void dump_pcie_capabilities(fbl::RefPtr<PcieDevice> dev, void* ctx) {
  bool is_first = true;
  lspci_params_t* params = static_cast<lspci_params_t*>(ctx);
  auto initial_indent = params->indent_level;
  params->indent_level += 2;

  if (!dev->capabilities().is_empty()) {
    LSPCI_PRINTF("Std Capabilities  :");
    for (const auto& cap : dev->capabilities()) {
      if (is_first) {
        printf(" %s (%#02x)\n", get_cap_str(cap.id()), cap.id());
        is_first = false;
        params->indent_level += 10;
      } else {
        LSPCI_PRINTF("%s (%#02x)\n", get_cap_str(cap.id()), cap.id());
      }
    }
  }

  params->indent_level = initial_indent;
}

static bool dump_pcie_device(const fbl::RefPtr<PcieDevice>& dev, void* ctx, uint level) {
  DEBUG_ASSERT(dev && ctx);
  lspci_params_t* params = (lspci_params_t*)ctx;
  bool match;
  auto cfg = dev->config();

  /* Grab the device's lock so it cannot be unplugged out from under us while
   * we print details. */
  fbl::AutoLock lock(dev->dev_lock());

  /* If the device has already been unplugged, just skip it */
  if (!dev->plugged_in())
    return true;

  match = (((params->bus_id == WILDCARD_ID) || (params->bus_id == dev->bus_id())) &&
           ((params->dev_id == WILDCARD_ID) || (params->dev_id == dev->dev_id())) &&
           ((params->func_id == WILDCARD_ID) || (params->func_id == dev->func_id())));
  if (!match)
    return true;

  if (!params->found && (params->bus_id != WILDCARD_ID)) {
    params->base_level = level;
  } else {
    DEBUG_ASSERT(!params->base_level);
  }

  params->found++;

  DEBUG_ASSERT(level >= params->base_level);
  params->indent_level = params->verbose ? 0 : level - params->base_level;

  /* Dump the header */
  dump_pcie_hdr(*dev, params);

  /* Only dump details if we are in verbose mode and this device matches our
   * filter */
  if (params->verbose) {
    params->indent_level += 2;

    dump_pcie_common(*dev, params);
    dump_pcie_bars(*dev, params);

    uint8_t header_type = cfg->Read(PciConfig::kHeaderType) & PCI_HEADER_TYPE_MASK;
    switch (header_type) {
      case PCI_HEADER_TYPE_STANDARD:
        dump_pcie_standard(*dev, params);
        break;

      case PCI_HEADER_TYPE_PCI_BRIDGE: {
        if (dev->is_bridge()) {
          dump_pcie_bridge(*static_cast<PcieBridge*>(dev.get()), params);
        } else {
          printf("ERROR! Type 1 header detected for non-bridge device!\n");
        }
      } break;

      case PCI_HEADER_TYPE_CARD_BUS:
        printf("TODO : Implemnt CardBus Config header register dump\n");
        break;

      default:
        printf("Unknown Header Type (0x%02x)\n", header_type);
        break;
    }

    params->indent_level -= 2;
    dump_pcie_capabilities(dev, params);
  }

  if (params->cfg_dump_amt)
    dump_pcie_raw_config(params->cfg_dump_amt, dev->config());

  return true;
}

int PcieDebugConsole::CmdLsPci(int argc, const cmd_args* argv, uint32_t flags) {
  lspci_params_t params;
  uint filter_ndx = 0;

  memset(&params, 0, sizeof(params));
  params.bus_id = WILDCARD_ID;
  params.dev_id = WILDCARD_ID;
  params.func_id = WILDCARD_ID;

  for (int i = 1; i < argc; ++i) {
    bool confused = false;

    if (argv[i].str[0] == '-') {
      const char* c = argv[i].str + 1;
      if (!(*c))
        confused = true;

      while (!confused && *c) {
        switch (*c) {
          case 'f':
            if (params.cfg_dump_amt < PCIE_BASE_CONFIG_SIZE)
              params.cfg_dump_amt = PCIE_BASE_CONFIG_SIZE;
            params.force_dump_cfg = true;
            break;

          case 'e':
            if (params.cfg_dump_amt < PCIE_EXTENDED_CONFIG_SIZE)
              params.cfg_dump_amt = PCIE_EXTENDED_CONFIG_SIZE;
            __FALLTHROUGH;

          case 'c':
            if (params.cfg_dump_amt < PCIE_BASE_CONFIG_SIZE)
              params.cfg_dump_amt = PCIE_BASE_CONFIG_SIZE;
            __FALLTHROUGH;

          case 'l':
            params.verbose = true;
            break;

          default:
            confused = true;
            break;
        }

        c++;
      }
    } else {
      switch (filter_ndx) {
        case 0:
          params.bus_id = static_cast<uint>(argv[i].i);
          if (params.bus_id >= PCIE_MAX_BUSSES)
            confused = true;
          break;

        case 1:
          params.dev_id = static_cast<uint>(argv[i].i);
          if (params.dev_id >= PCIE_MAX_DEVICES_PER_BUS)
            confused = true;
          break;

        case 2:
          params.func_id = static_cast<uint>(argv[i].i);
          if (params.func_id >= PCIE_MAX_FUNCTIONS_PER_DEVICE)
            confused = true;
          break;

        default:
          confused = true;
          break;
      }

      filter_ndx++;
    }

    if (confused) {
      printf("usage: %s [-t] [-l] [<bus_id>] [<dev_id>] [<func_id>]\n", argv[0].str);
      printf("       -l : Be verbose when dumping info about discovered devices.\n");
      printf("       -c : Dump raw standard config (implies -l)\n");
      printf("       -e : Dump raw extended config (implies -l -c)\n");
      printf(
          "       -f : Force dump at least standard config, even if the device didn't "
          "enumerate (requires a full BDF address)\n");
      return ZX_OK;
    }
  }

  auto bus_drv = PcieBusDriver::GetDriver();
  if (bus_drv == nullptr)
    return ZX_ERR_BAD_STATE;

  bus_drv->ForeachDevice(dump_pcie_device, &params);

  if (!params.found && params.force_dump_cfg && (params.bus_id != WILDCARD_ID) &&
      (params.dev_id != WILDCARD_ID) && (params.func_id != WILDCARD_ID)) {
    const PciConfig* cfg;

    cfg = bus_drv->GetConfig(params.bus_id, params.dev_id, params.func_id);
    if (!cfg) {
      printf("Config space for %02x:%02x.%01x not mapped by bus driver!\n", params.bus_id,
             params.dev_id, params.func_id);
    } else {
      dump_pcie_raw_config(params.cfg_dump_amt, cfg);
    }
  } else {
    printf("PCIe scan discovered %u device%s\n", params.found, (params.found == 1) ? "" : "s");
  }

  return ZX_OK;
}

int PcieDebugConsole::CmdPciUnplug(int argc, const cmd_args* argv, uint32_t flags) {
  bool confused = false;
  uint bus_id, dev_id, func_id;

  if (argc == 4) {
    bus_id = static_cast<uint>(argv[1].i);
    dev_id = static_cast<uint>(argv[2].i);
    func_id = static_cast<uint>(argv[3].i);

    if ((bus_id >= PCIE_MAX_BUSSES) || (dev_id >= PCIE_MAX_DEVICES_PER_BUS) ||
        (func_id >= PCIE_MAX_FUNCTIONS_PER_DEVICE))
      confused = true;
  } else {
    confused = true;
  }

  if (confused) {
    printf("usage: %s <bus_id> <dev_id> <func_id>\n", argv[0].str);
    return ZX_OK;
  }

  auto bus_drv = PcieBusDriver::GetDriver();
  if (bus_drv == nullptr)
    return ZX_ERR_BAD_STATE;

  fbl::RefPtr<PcieDevice> dev = bus_drv->GetRefedDevice(bus_id, dev_id, func_id);

  if (!dev) {
    printf("Failed to find PCI device %02x:%02x.%01x\n", bus_id, dev_id, func_id);
  } else {
    printf("Unplugging PCI device %02x:%02x.%x...\n", bus_id, dev_id, func_id);
    dev->Unplug();
    dev = nullptr;
    printf("done\n");
  }

  return ZX_OK;
}

int PcieDebugConsole::CmdPciReset(int argc, const cmd_args* argv, uint32_t flags) {
  bool confused = false;
  uint bus_id, dev_id, func_id;

  if (argc == 4) {
    bus_id = static_cast<uint>(argv[1].i);
    dev_id = static_cast<uint>(argv[2].i);
    func_id = static_cast<uint>(argv[3].i);

    if ((bus_id >= PCIE_MAX_BUSSES) || (dev_id >= PCIE_MAX_DEVICES_PER_BUS) ||
        (func_id >= PCIE_MAX_FUNCTIONS_PER_DEVICE))
      confused = true;
  } else {
    confused = true;
  }

  if (confused) {
    printf("usage: %s <bus_id> <dev_id> <func_id>\n", argv[0].str);
    return ZX_OK;
  }

  auto bus_drv = PcieBusDriver::GetDriver();
  if (bus_drv == nullptr)
    return ZX_ERR_BAD_STATE;

  fbl::RefPtr<PcieDevice> dev = bus_drv->GetRefedDevice(bus_id, dev_id, func_id);

  if (!dev) {
    printf("Failed to find PCI device %02x:%02x.%01x\n", bus_id, dev_id, func_id);
  } else {
    printf("Attempting reset of device %02x:%02x.%01x...\n", bus_id, dev_id, func_id);
    zx_status_t res = dev->DoFunctionLevelReset();
    dev = nullptr;
    if (res != ZX_OK)
      printf("Reset attempt failed (res = %d).\n", res);
    else
      printf("Success, device %02x:%02x.%01x has been reset.\n", bus_id, dev_id, func_id);
  }

  return ZX_OK;
}

int PcieDebugConsole::CmdPciRescan(int argc, const cmd_args* argv, uint32_t flags) {
  auto bus_drv = PcieBusDriver::GetDriver();
  if (bus_drv == nullptr)
    return ZX_ERR_BAD_STATE;

  return bus_drv->RescanDevices();
}

int PcieDebugConsole::CmdPciRegionDump(int argc, const cmd_args* argv, uint32_t flags) {
  auto walk_cb = [](const ralloc_region_t* r) -> bool {
    printf("\tregion { base = %" PRIxPTR ", size = %#lx }\n", r->base, r->size);
    return true;
  };

  auto bus_drv = PcieBusDriver::GetDriver();
  printf("mmio_low:\n");
  bus_drv->mmio_lo_regions_.WalkAllocatedRegions(walk_cb);
  printf("mmio_high:\n");
  bus_drv->mmio_hi_regions_.WalkAllocatedRegions(walk_cb);
  printf("pio:\n");
  bus_drv->pio_regions_.WalkAllocatedRegions(walk_cb);

  return ZX_OK;
}

STATIC_COMMAND_START
STATIC_COMMAND("lspci", "Enumerate the devices detected in PCIe ECAM space",
               &PcieDebugConsole::CmdLsPci)
STATIC_COMMAND("pciunplug", "Force \"unplug\" the specified PCIe device",
               &PcieDebugConsole::CmdPciUnplug)
STATIC_COMMAND("pcireset", "Initiate a Function Level Reset of the specified device.",
               &PcieDebugConsole::CmdPciReset)
STATIC_COMMAND("pcirescan",
               "Force a rescan of the PCIe configuration space, matching drivers to unclaimed "
               "devices as we go.  Then attempt to start all newly claimed devices.",
               &PcieDebugConsole::CmdPciRescan)
STATIC_COMMAND("pciregions", "Dump information on present PCI address region allocations",
               &PcieDebugConsole::CmdPciRegionDump)
STATIC_COMMAND_END(pcie)
